package net.newsmth.smthapp.android.widget;

import android.content.Context;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.newsmth.smthapp.android.R;

/**
 * Created by Hatsune Miku on 4/20/14.
 */
public class PullToRefreshLayout extends LinearLayout implements View.OnTouchListener {
    public static final int STATUS_START = 0;  // Figure click but not move
    public static final int STATUS_PULL_TO_REFRESH = 1;
    public static final int STATUS_RELEASE_TO_REFRESH = 2;
    public static final int STATUS_REFRESHING = 3;
    public static final int STATUS_REFRESH_FINISHED = 4;

    public static final int SCROLL_SPEED = -20;  //scroll back speed

    // The time represented by milliseconds
    public static final long ONE_MINUTE = 60 * 1000;
    public static final long ONE_HOUR = 60 * ONE_MINUTE;
    public static final long ONE_DAY = 24 * ONE_HOUR;
    public static final long ONE_MONTH = 30 * ONE_DAY;
    public static final long ONE_YEAR = 365 * ONE_DAY;


    private PullToRefreshListener mlistener;
    private View header;
    private ListView listView;

    private ProgressBar progressBar;
    private ImageView arrow;
    private TextView description;
    private TextView updateAt;
    private MarginLayoutParams headerLayoutParams;

    private long lastRefreshTime = System.currentTimeMillis();  // Record the refresh time.
    private int headerHeight;

    private int currentStatus =  STATUS_START;
    private int lastStatus = currentStatus;

    // Distance in Y-axis when finger move
    private float yDown;

    // After the distance is greater than this slop, the scroll begin
    private int touchSlop;
    private boolean hasLoaded;
    private boolean ableToPull;

    private static Logger logger = LoggerFactory.getLogger(PullToRefreshLayout.class);

    public ListView getListView() {
        return listView;
    }

    public void setListView(ListView listView) {
        if (listView != null) {
            this.listView = listView;
            listView.setOnTouchListener(this);
        }
    }

    public PullToRefreshListener getPullToRefreshListener() {
        return mlistener;
    }

    public void setPullToRefreshListener(PullToRefreshListener mlistener) {
        this.mlistener = mlistener;
    }


    public PullToRefreshLayout(Context context) {
        super(context);
        init(context);
    }

    public PullToRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    public void finishRefreshing() {
        currentStatus = STATUS_REFRESH_FINISHED;
        new HideHeaderTask().execute();
    }

    private void init(Context context) {
        header = LayoutInflater.from(context).inflate(R.layout.view_pull_to_refresh_header, null);
        progressBar = (ProgressBar)header.findViewById(R.id.progress_bar);
        arrow = (ImageView)header.findViewById(R.id.arrow);
        description = (TextView)header.findViewById(R.id.description);
        updateAt = (TextView)header.findViewById(R.id.updated_at);
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        refreshUpdatedAtValue();
        setOrientation(VERTICAL);
        addView(header, 0);
    }

    private void refreshUpdatedAtValue() {
        long currentTime = System.currentTimeMillis();
        long timePassed = currentTime - lastRefreshTime;

        long timeInFormat;
        String updatedAtValue;

        Resources resources = getResources();

        if (timePassed < 0) {
            updatedAtValue = resources.getString(R.string.time_error);
        } else if (timePassed < ONE_MINUTE) {
            updatedAtValue = resources.getString(R.string.update_just_now);
        } else if (timePassed < ONE_HOUR) {
            timeInFormat = timePassed / ONE_MINUTE;
            String value = timeInFormat + resources.getString(R.string.time_minute);
            updatedAtValue = String.format(resources.getString(R.string.updated_at), value);
        } else if (timePassed < ONE_DAY) {
            timeInFormat = timePassed / ONE_HOUR;
            String value = timeInFormat + resources.getString(R.string.time_hour);
            updatedAtValue = String.format(resources.getString(R.string.updated_at), value);
        } else if (timePassed < ONE_MONTH) {
            timeInFormat = timePassed / ONE_DAY;
            String value = timeInFormat + resources.getString(R.string.time_day);
            updatedAtValue = String.format(resources.getString(R.string.updated_at), value);
        } else if (timePassed < ONE_YEAR) {
            timeInFormat = timePassed / ONE_MONTH;
            String value = timeInFormat + resources.getString(R.string.time_once_month);
            updatedAtValue = String.format(resources.getString(R.string.updated_at), value);
        } else if (timePassed >= ONE_YEAR) {
            timeInFormat = timePassed / ONE_YEAR;
            String value = timeInFormat + resources.getString(R.string.time_year);
            updatedAtValue = String.format(resources.getString(R.string.updated_at), value);
        } else {
            updatedAtValue = resources.getString(R.string.time_error);
        }
        updateAt.setText(updatedAtValue);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        if (changed && !hasLoaded) {
            headerHeight = -1 * header.getHeight();
            headerLayoutParams = (MarginLayoutParams)header.getLayoutParams();
            headerLayoutParams.topMargin = headerHeight;
            header.setLayoutParams(headerLayoutParams);
            hasLoaded = true;
        }
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {

        return onTouchInternal(motionEvent);
//        return false;
    }

    private boolean onTouchInternal(MotionEvent motionEvent) {
        setIsAbleToPull(motionEvent);
        logger.debug("onTouch Start");

        if (ableToPull) {
            return pullToRefresh(motionEvent);
        }
        return false;
    }

    private boolean pullToRefresh(MotionEvent motionEvent) {

        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                yDown = motionEvent.getRawY();
                currentStatus = STATUS_START;
                return true;

            case MotionEvent.ACTION_MOVE:
                boolean refreshStart = pullToUpdateWhenMove(motionEvent);

                if (!refreshStart) {
                    return false;
                }

                break;
            case MotionEvent.ACTION_UP:
            default:
                if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
                    new RefreshingTask().execute();
                } else if (currentStatus == STATUS_PULL_TO_REFRESH) {
                    new HideHeaderTask().execute();
                }


        }

        if (currentStatus == STATUS_PULL_TO_REFRESH ||
                currentStatus == STATUS_RELEASE_TO_REFRESH) {
            updateHeaderView();
        }

        lastStatus = currentStatus;
        return (currentStatus != STATUS_START && currentStatus != STATUS_REFRESH_FINISHED);
    }

    /**
     * Implement pull to refresh when move
     * @param motionEvent
     * @return indicate if the refresh process start
     */
    private boolean pullToUpdateWhenMove(MotionEvent motionEvent) {
        float yMove  = motionEvent.getRawY();
        int distance = (int) (yMove - yDown);

        if (distance <= 1 && headerLayoutParams.topMargin <= headerHeight) {
            currentStatus = STATUS_START;
            return false;
        }

        if (distance < touchSlop) {
            return false;
        }

        if (currentStatus != STATUS_REFRESHING) {
            if (headerLayoutParams.topMargin > 0) {
                currentStatus = STATUS_RELEASE_TO_REFRESH;
            } else {
                currentStatus = STATUS_PULL_TO_REFRESH;
            }

            // Implement the pull effective through changing the top margin of the header
            headerLayoutParams.topMargin = (distance / 2) + headerHeight;
            header.setLayoutParams(headerLayoutParams);
        }
        return true;
    }

    public void setIsAbleToPull(MotionEvent event) {
        View firstChild = listView.getChildAt(0);

        if (firstChild != null) {
            int firstVisiblePosition = listView.getFirstVisiblePosition();

            // Enable the pull when the distance from the top edge of the first element to the parent
            // is 0
            if (firstVisiblePosition == 0 && firstChild.getTop() == 0) {
                if (!ableToPull) {
                    yDown = event.getRawY();
                }
                ableToPull = true;
            } else {
                if (headerLayoutParams.topMargin != headerHeight) {
                    headerLayoutParams.topMargin = headerHeight;
                    header.setLayoutParams(headerLayoutParams);
                }
                ableToPull = false;
            }

        } else {
            // Enable the pull if the listview is empty
            ableToPull = true;
        }
    }

    private void sleep(int time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException ex) {

        }
    }

    private void updateHeaderView() {
        if (lastStatus != currentStatus) {
            if (currentStatus == STATUS_PULL_TO_REFRESH) {
                description.setText(R.string.pull_to_refresh);
                arrow.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.INVISIBLE);
                rotateArrow();
            } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
                description.setText(R.string.release_to_refresh);
                arrow.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.INVISIBLE);
                rotateArrow();
            } else if (currentStatus == STATUS_REFRESHING) {
                description.setText(R.string.refreshing);
                progressBar.setVisibility(View.VISIBLE);
                arrow.clearAnimation();
                arrow.setVisibility(View.INVISIBLE);
            }

            refreshUpdatedAtValue();
        }
    }

    private void rotateArrow() {
        float pivotX = arrow.getWidth() / 2f;
        float pivotY = arrow.getHeight() / 2f;

        float fromDegrees = 0f;
        float toDegrees = 0f;

        if (currentStatus == STATUS_PULL_TO_REFRESH) {
            fromDegrees = 180f;
            toDegrees = 360f;
        } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
            fromDegrees = 0f;
            toDegrees = 180f;
        }

        RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY);
        animation.setDuration(200);
        animation.setFillAfter(true);
        arrow.startAnimation(animation);
    }

    //region Other class
    class RefreshingTask extends AsyncTask<Void, Integer, Void> {

        @Override
        protected Void doInBackground(Void... voids) {
            int topMargin = headerLayoutParams.topMargin;

            while (true) {
                topMargin += SCROLL_SPEED;

                if (topMargin <= 0) {
                    topMargin = 0;
                    break;
                }

                publishProgress(topMargin);
                sleep(10);
            }

            currentStatus = STATUS_REFRESHING;
            publishProgress(0);

            if (mlistener != null) {
                mlistener.onRefresh();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);

            updateHeaderView();
            headerLayoutParams.topMargin = values[0];
            header.setLayoutParams(headerLayoutParams);
        }
    }

    class HideHeaderTask extends AsyncTask<Void, Integer, Integer> {

        @Override
        protected Integer doInBackground(Void... voids) {
            int topMargin = headerLayoutParams.topMargin;

            while (true) {
                topMargin += SCROLL_SPEED;

                if (topMargin <= headerHeight) {
                    topMargin = headerHeight;
                    publishProgress(topMargin);
                    break;
                }

                publishProgress(topMargin);
                sleep(10);
            }
            return topMargin;
        }

        @Override
        protected void onPostExecute(Integer topMargin) {
            super.onPostExecute(topMargin);

            currentStatus = STATUS_REFRESH_FINISHED;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);

            headerLayoutParams.topMargin = values[0];
            header.setLayoutParams(headerLayoutParams);
        }
    }

    public interface PullToRefreshListener {
        void onRefresh();
    }
    //endregion
}
